﻿#region Copyright
//       Copyright (c) 2009 - Antoine Jaussoin - http://www.jaussoin.com/blog/
// 
//        This program is free software: you can redistribute it and/or modify
//        it under the terms of the GNU General Public License as published by
//        the Free Software Foundation, either version 3 of the License, or
//        (at your option) any later version.
// 
//        This program is distributed in the hope that it will be useful,
//        but WITHOUT ANY WARRANTY; without even the implied warranty of
//        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//        GNU General Public License for more details.
// 
//        You should have received a copy of the GNU General Public License
//        along with this program.  If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace FT.Architecture.Utils.ExtensionMethods
{
    /// <summary>
    /// Extension methods for collections
    /// </summary>
    public static class EnumerableExtensions
    {
        /// <summary>
        /// Joins in the form 'this, that and other' using 3.5 syntax
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public static string NaturalSentenceString<T>(this IEnumerable<T> items)
        {
            var b = new StringBuilder();
            string previous = null;
            int count = 0;
            foreach (T item in items)
            {
                if (previous != null)
                {
                    b.Append(previous);
                    b.Append(", ");
                }

                previous = item.ToString();
                count++;
            }
            if (count == 0)
                return string.Empty;
            if (count == 1)
                return previous;

            b.Remove(b.Length - 2, 2);
            b.Append(" and ");
            b.Append(previous);

            return b.ToString();
        }

        /// <summary>
        /// Join a collection of items using a string.  Objects will be stringified
        /// using ToString
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection">The collection to join</param>
        /// <param name="separator">A string to separate each item</param>
        /// <returns></returns>
        public static string Join<T>(this IEnumerable<T> collection, string separator)
        {
            StringBuilder b = new StringBuilder();
            foreach (T t in collection)
            {
                b.Append(t.ToString());
                b.Append(separator);
            }

            if (b.Length == 0)
                return "";

            b.Remove(b.Length - separator.Length, separator.Length);

            return b.ToString();
        }

        /// <summary>
        /// Returns a <see cref="IDictionary"/> where all objects given in ListToIndex are indexed by IndexProperty (which becomes the key of the dictionary)
        /// </summary>
        /// <typeparam name="TKey">Key type</typeparam>
        /// <typeparam name="TValue">Value type</typeparam>
        /// <param name="listToIndex">List to index</param>
        /// <param name="indexProperty">Property used as index</param>
        /// <returns>Dictionary</returns>
        public static IDictionary<TKey, IList<TValue>> Index<TKey, TValue>(this IEnumerable listToIndex,
                                                                           string indexProperty)
        {
            IDictionary<TKey, IList<TValue>> list = new Dictionary<TKey, IList<TValue>>();

            foreach (TValue o in listToIndex)
            {
                TKey key = Reflection.Reflection.GetProperty<TKey>(o, indexProperty);
                if (!Equals(key, default(TKey)) && !list.ContainsKey(key))
                    list.Add(key, new List<TValue>());

                if (!Equals(key, default(TKey)))
                    list[key].Add(o);
            }

            return list;
        }

        /// <summary>
        /// For some reason the linq Example of All that takes an index doesn't exist
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
        {
            int i = 0;
            foreach (TSource item in source)
            {
                if (!predicate(item, i++))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Remove duplicate entries in a list
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="originalList"></param>
        /// <returns></returns>
        public static IList<T> RemoveDuplicates<T>(this IEnumerable<T> originalList)
        {
            IList<T> newList = new List<T>();

            Dictionary<int, T> dic = new Dictionary<int, T>();

            foreach (T t in originalList)
            {
                if (!dic.ContainsKey(t.GetHashCode()))
                {
                    dic.Add(t.GetHashCode(), t);
                    newList.Add(t);
                }
            }

            return newList;
        }

        /// <summary>
        /// Extract a given member on all objects in a list and return them as a new list
        /// </summary>
        /// <param name="originalList"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static IEnumerable<TExtractedType> Extract<TExtractedType, TItem>(this IEnumerable<TItem> originalList,
                                                                                 string propertyName)
        {
            List<TExtractedType> newList = new List<TExtractedType>();

            foreach (TItem item in originalList)
            {
                TExtractedType extractedValue = Reflection.Reflection.GetProperty<TExtractedType>(item, propertyName);
                newList.Add(extractedValue);
            }

            return newList;
        }

        /// <summary>
        /// Replace all item of the collection by the ones given in parameter.
        /// Does a Clear() on the list, and loop through the <paramref name="newItems"/> collection and add each item in the cleared list.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="oldList"></param>
        /// <param name="newItems"></param>
        /// <returns></returns>
        public static ICollection<T> ReplaceAll<T>(this ICollection<T> oldList, ICollection<T> newItems)
        {
            oldList.Clear();

            foreach (T item in newItems)
            {
                oldList.Add(item);
            }

            return oldList;
        }

        /// <summary>
        /// Gets a subset of the list.  Indices are inclusive
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="fromIndex"></param>
        /// <param name="toIndex"></param>
        /// <returns></returns>
        public static IList<T> Subset<T>(this IList<T> list, int fromIndex, int toIndex)
        {
            IList<T> newList = new List<T>();

            int i = 0;
            foreach (T item in list)
            {
                if (i < fromIndex)
                {
                    i++;
                    continue;
                }

                if (i > toIndex)
                    break;

                newList.Add(item);
                i++;
            }

            return newList;
        }

        /// <summary>
        /// Joins an Array of any type of object together as a string separated
        /// by separator.  This is done by extracting member value on each item in the list
        /// </summary>
        /// <typeparam name="T">Any type</typeparam>
        /// <param name="separator">A String to separate each object</param>
        /// <param name="member">Member to read from the list of objects</param>
        /// <param name="values">A list of values to concatenate</param>
        /// <returns></returns>
        public static string JoinAsString<T>(this IEnumerable<T> values, string separator, string member)
        {
            List<string> array = new List<string>();

            foreach (T value in values)
            {
                array.Add(Reflection.Reflection.GetProperty<string>(value, member));
            }

            return array.Join(separator);
        }
    }
}